﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Windows;
using System.Windows.Media;

using FirstFloor.Documents.IO;
using FirstFloor.Documents.Pdf.Fonts;

namespace FirstFloor.Documents.Pdf
{
    /// <summary>
    /// Represents the context during a page parse operation.
    /// </summary>
    internal class PageParserContext
        : ProcessorContext
    {
        private Stack<GraphicsState> states = new Stack<GraphicsState>();
        private GraphicsState state = new GraphicsState();
        private StringBuilder currentPathData = new StringBuilder();

        private PageParserContext(bool isFormContext, LoadSettings settings)
            : base(settings)
        {
            this.IsFormContext = isFormContext;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is a form context.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is form context; otherwise, <c>false</c>.
        /// </value>
        public bool IsFormContext { get; private set; }
        /// <summary>
        /// Defines the boundaries of the physical medium.
        /// </summary>
        /// <value>The media box.</value>
        public Rect MediaBox { get; private set; }
        /// <summary>
        /// Defines the region to which the contents of the page are to be clipped.
        /// </summary>
        /// <value>The crop box.</value>
        public Rect CropBox { get; private set; }
        /// <summary>
        /// Gets or sets the writer.
        /// </summary>
        /// <value>The writer.</value>
        public XmlWriter Writer { get; private set; }
        /// <summary>
        /// Gets the PDF parser.
        /// </summary>
        /// <value>The parser.</value>
        public PdfParser Parser { get; private set; }
        /// <summary>
        /// Gets the resource dictionary contained in the page dictionary.
        /// </summary>
        /// <value>The resource dictionary.</value>
        public DictionaryObject Resources { get; private set; }
        /// <summary>
        /// Gets or sets the current graphics state.
        /// </summary>
        /// <value>The state.</value>
        public GraphicsState State
        {
            get { return this.state; }
            set
            {
                this.state = value;
                UpdateCurrentFont();
            }
        }

        /// <summary>
        /// Gets or sets the current point in the path data.
        /// </summary>
        /// <value>The current path point.</value>
        public Point CurrentPathPoint { get; set; }
        /// <summary>
        /// Gets the current font.
        /// </summary>
        /// <value>The current font.</value>
        public FontBase CurrentFont { get; private set; }

        /// <summary>
        /// Updates the current font.
        /// </summary>
        public void UpdateCurrentFont()
        {
            UpdateCurrentFont(null);
        }

        /// <summary>
        /// Updates the current font to specified font dictionary.
        /// </summary>
        /// <param name="fontDictionary">The font dictionary.</param>
        public void UpdateCurrentFont(DictionaryObject fontDictionary)
        {
            if (this.IsFormContext) {
                return;
            }
            if (fontDictionary == null && this.state.Tf != null) {
                var fonts = this.Resources.Get<DictionaryObject>("Font");
                fontDictionary = fonts.Get<DictionaryObject>(this.State.Tf, null);
            }
            if (fontDictionary == null) {
                this.CurrentFont = null;
            }
            else {
                this.CurrentFont = this.Parser.GetFont(fontDictionary);
            }
        }

        /// <summary>
        /// Adds data to the current path.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="o">The o.</param>
        public void AddPathData(string data, params object[] o)
        {
            if (this.currentPathData.Length > 0) {
                this.currentPathData.Append(' ');
            }
            this.currentPathData.Append(string.Format(data, o));
        }

        /// <summary>
        /// Gets the current path data.
        /// </summary>
        /// <returns></returns>
        public string GetPathData()
        {
            return this.currentPathData.ToString();
        }

        /// <summary>
        /// Clears the path data.
        /// </summary>
        public void ClearPathData()
        {
            this.currentPathData = new StringBuilder();
            this.CurrentPathPoint = new Point();
        }

        /// <summary>
        /// Adds a curve segment to the path data.
        /// </summary>
        /// <param name="x1">The x1.</param>
        /// <param name="y1">The y1.</param>
        /// <param name="x2">The x2.</param>
        /// <param name="y2">The y2.</param>
        /// <param name="x3">The x3.</param>
        /// <param name="y3">The y3.</param>
        public void AddPathCurveSegment(double x1, double y1, double x2, double y2, double x3, double y3)
        {
            AddPathData("C{0},{1} {2},{3} {4},{5}", x1.Format(), y1.Format(), x2.Format(), y2.Format(), x3.Format(), y3.Format());
            this.CurrentPathPoint = new Point(x3, y3);
        }

        /// <summary>
        /// Saves the current graphics state to the stack.
        /// </summary>
        public void SaveState()
        {
            this.states.Push(this.State.Copy());
        }

        /// <summary>
        /// Restores a previously saved graphics state.
        /// </summary>
        public void RestoreState()
        {
            this.State = this.states.Pop();
        }

        /// <summary>
        /// Creates a parse context for specified page.
        /// </summary>
        /// <param name="pageObject">The page object.</param>
        /// <param name="writer">The writer.</param>
        /// <param name="settings">The settings.</param>
        /// <returns></returns>
        public static PageParserContext Create(DictionaryObject pageObject, XmlWriter writer, LoadSettings settings)
        {
            var context = new PageParserContext(false, settings) {
                Parser = pageObject.Owner,
                Resources = pageObject.Get<DictionaryObject>("Resources"),
                MediaBox = pageObject.Get<ArrayObject>("MediaBox").CreateRectangle(),
                Writer = writer
            };

            context.CropBox = context.MediaBox;     // default is MediaBox
            ArrayObject cropBox;
            if (pageObject.TryGet("CropBox", out cropBox)){
                context.CropBox = cropBox.CreateRectangle();
            }

            return context;
        }

        /// <summary>
        /// Creates a parse form context based on this instance.
        /// </summary>
        /// <returns></returns>
        public PageParserContext CreateFormContext(DictionaryObject form)
        {
            return new PageParserContext(true, null) {
                Parser = this.Parser,
                Resources = form.Get<DictionaryObject>("Resources", null),
                MediaBox = form.Get<ArrayObject>("BBox").CreateRectangle(),     //TODO: do not re-use PageParserContent, create a FormParserContext
                CurrentFont = this.CurrentFont,
                State = this.State,
                Writer = this.Writer
            };
        }
    }
}
